package com.example.codequalityservice.serviceImpl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.fastjson.JSON;
import com.example.api.service.CodeQualityService;
import com.example.common.config.ThreadPool;
import com.example.common.constant.Constants;
import com.example.common.entity.*;
import com.example.common.exception.BaseException;
import com.example.common.exception.ExceptionCode;
import com.example.common.mapper.CodeQualityResultMapper;
import com.example.common.utils.GitUtils;
import com.example.common.utils.GithubApiUtils;
import com.example.common.utils.PythonScriptCallable;
import com.example.common.utils.SnowFlakeUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * @author DestinyCc
 * @date 2022/4/16
 */
@Service(protocol = "dubbo")
@Slf4j
public class CodeQualityServiceImpl implements CodeQualityService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    CodeQualityResultMapper codeQualityResultMapper;

    @Override
    @SneakyThrows
    public Long calculateCodeQuality(String appId, String tenantId, String owner, String repo) {
        String projectLocation = Constants.DEFAULT_PROJECT_LOCATION + owner + "/" + repo;
        File project = new File(projectLocation);
        if (!project.exists()){
            GitUtils.clone(owner, repo);
        }else {
            GitUtils.pull(owner, repo);
        }
        Long resultId = SnowFlakeUtil.nextId();
        codeQualityResultMapper.insertSelective(new CodeQualityResult(resultId, tenantId, owner, repo));
        String codeQualityCmd = String.format(Constants.CODE_QUALITY_PYTHON_SCRIPT, projectLocation, owner + "/" + repo);
        String reliabilityCmd = String.format(Constants.RELIABILITY_CAL_PYTHON_SCRIPT, projectLocation, owner + "/" + repo);
        CodeQualityCalMsg calMsg = new CodeQualityCalMsg(tenantId, appId, resultId, codeQualityCmd, reliabilityCmd);
        stringRedisTemplate.opsForList().leftPush(Constants.CODE_QUALITY_CAL_MSG_NAME, JSON.toJSONString(calMsg));
        return resultId;
    }

    @Override
    public CodeQualityResult getCodeQualityResult(Long resultId, String tenantId) {
        CodeQualityResult codeQualityResult = codeQualityResultMapper.selectByPrimaryKey(resultId);
        if (Objects.isNull(codeQualityResult)){
            throw new BaseException(ExceptionCode.PARAM_NOT_VALID, "resultId不合法");
        }
        if (String.valueOf(codeQualityResult.getTenantId()).equals(tenantId)){
            return codeQualityResult;
        }else {
            throw new BaseException(ExceptionCode.PARAM_NOT_VALID, "resultId和tenantId不匹配");
        }
    }

    @Override
    @SneakyThrows
    public List<CodeQualityForPR> getPRCodeQuality(String repoPath, String remotePath) {
        String prevCmd = String.format(Constants.CODE_QUALITY_FOR_DIFF_PYTHON_SCRIPT, repoPath, remotePath, "prev");
        String currCmd = String.format(Constants.CODE_QUALITY_FOR_DIFF_PYTHON_SCRIPT, repoPath, remotePath, "curr");
        log.info("prevCmd = {}", prevCmd);
        log.info("currCmd = {}", currCmd);
        PythonScriptCallable prevCallable = new PythonScriptCallable(prevCmd);
        PythonScriptCallable currCallable = new PythonScriptCallable(currCmd);
        FutureTask<String> prevFutureTask = new FutureTask<>(prevCallable);
        FutureTask<String> currFutureTask = new FutureTask<>(currCallable);
        ThreadPool.executor.submit(prevFutureTask);
        ThreadPool.executor.submit(currFutureTask);
        while (!prevFutureTask.isDone()){
            TimeUnit.SECONDS.sleep(1);
        }
        while (!currFutureTask.isDone()){
            TimeUnit.SECONDS.sleep(1);
        }
        String prevResultFilename = prevFutureTask.get();
        String currResultFilename = currFutureTask.get();
        log.info("prevFilename = {}", prevResultFilename);
        log.info("currFilename = {}", currResultFilename);
        Map<String, CodeQualityForPR.Quality> codeQualityForPRprev = new HashMap<>();
        Map<String, CodeQualityForPR.Quality> codeQualityForPRcurr = new HashMap<>();
        EasyExcel.read(Constants.CODE_QUALITY_FOR_DIFF_PROJECT_LOCATION + prevResultFilename, CodeQualityXlsxForPR.class, new PageReadListener<CodeQualityXlsxForPR>(dataList -> {
            for (CodeQualityXlsxForPR quality : dataList) {
                log.info("读取到一条数据{}", JSON.toJSONString(quality));
                String fullName = quality.getFilename();
                String[] data = fullName.split("/");
                String filename = data[data.length-2].split("\\.")[0];
                String suffix = data[data.length-1];
                StringBuilder fullFilename = new StringBuilder();
                if (!Objects.equals(suffix, filename)){
                    continue;
                }
                for (int i = 3; i <data.length-2 ; i++) {
                    fullFilename.append(data[i]).append("/");
                }
                fullFilename.append(filename).append(".java");
                CodeQualityForPR.Quality prev = CodeQualityForPR.Quality.builder()
                        .reuseablity(quality.getReusability())
                        .complexity(quality.getComplexity())
                        .extendability(quality.getExtendibility())
                        .inheritance(quality.getInheritance())
                        .maintainability(quality.getMaintainability())
                        .readability(quality.getReadability())
                        .security(quality.getSecurity())
                        .testability(quality.getTestablity())
                        .build();
                codeQualityForPRprev.put(fullFilename.toString(), prev);
            }
        })).sheet().doRead();
        EasyExcel.read(Constants.CODE_QUALITY_FOR_DIFF_PROJECT_LOCATION + currResultFilename, CodeQualityXlsxForPR.class, new PageReadListener<CodeQualityXlsxForPR>(dataList -> {
            for (CodeQualityXlsxForPR quality : dataList) {
                log.info("读取到一条数据{}", JSON.toJSONString(quality));
                String fullName = quality.getFilename();
                String[] data = fullName.split("/");
                String filename = data[data.length-2].split("\\.")[0];
                String suffix = data[data.length-1];
                StringBuilder fullFilename = new StringBuilder();
                if (!Objects.equals(suffix, filename)){
                    continue;
                }
                for (int i = 3; i <data.length-2 ; i++) {
                    fullFilename.append(data[i]).append("/");
                }
                fullFilename.append(filename).append(".java");
                CodeQualityForPR.Quality curr = CodeQualityForPR.Quality.builder()
                        .reuseablity(quality.getReusability())
                        .complexity(quality.getComplexity())
                        .extendability(quality.getExtendibility())
                        .inheritance(quality.getInheritance())
                        .maintainability(quality.getMaintainability())
                        .readability(quality.getReadability())
                        .security(quality.getSecurity())
                        .testability(quality.getTestablity())
                        .build();
                codeQualityForPRcurr.put(fullFilename.toString(), curr);
            }
        })).sheet().doRead();
        List<CodeQualityForPR> codeQualityForPRResult = new ArrayList<>();
        for (Map.Entry<String, CodeQualityForPR.Quality> entry: codeQualityForPRcurr.entrySet()){
            String key = entry.getKey();
            CodeQualityForPR codeQualityForPR = new CodeQualityForPR();
            codeQualityForPR.setFilename(key);
            CodeQualityForPR.Quality prevQuality = codeQualityForPRprev.get(key);
            codeQualityForPR.setCurr(entry.getValue());
            if (Objects.isNull(prevQuality)){
                codeQualityForPR.setPrev(null);
                codeQualityForPR.setDeviation(entry.getValue());
            }else {
                codeQualityForPR.setPrev(prevQuality);
                codeQualityForPR.setDeviation(entry.getValue().compare(prevQuality));
            }
            codeQualityForPRResult.add(codeQualityForPR);
        }
        return codeQualityForPRResult;
    }

    @Override
    public void extractFiles(PRInfo prInfo) {
        String requestUrl = String.format(Constants.GITHUB.GET_COMMITS_URL, prInfo.getOwner(), prInfo.getRepo(), prInfo.getPullNumber());
        List<LinkedHashMap<String, Object>> commitList = (List<LinkedHashMap<String, Object>>) GithubApiUtils.callGithubApi(requestUrl);
        List<String> parentId = new ArrayList<>();
        LinkedHashMap<String, Object> firstCommit = commitList.get(0);
        List<LinkedHashMap<String, String>> parents = (List<LinkedHashMap<String, String>>) firstCommit.get("parents");
        for (LinkedHashMap<String, String> parent: parents){
            parentId.add(parent.get("sha"));
        }
        for (String id: parentId){
            extractChangedFiles(prInfo.getOwner(), prInfo.getRepo(), prInfo.getPullNumber(), id);
        }
    }

    private void extractChangedFiles(String owner, String repo, String pullNumber, String parentId) {
        String requestUrl = String.format(Constants.GITHUB.LIST_PULL_REQUESTS_FILES, owner, repo, pullNumber);
        List<ChangedFile> changedFiles = new ArrayList<>();
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(requestUrl);
            builder.queryParam("page", i);
            String uri = builder.build(true).toUriString();
            List<LinkedHashMap<String, Object>> files = (List<LinkedHashMap<String, Object>>) GithubApiUtils.callGithubApi(uri);
            if (files.size() == 0){
                break;
            }
            files.stream().map(f-> new ChangedFile((String) f.get("filename"),
                    (String) f.get("raw_url"),
                    (String) f.get("status"))).filter(Objects::nonNull).forEach(changedFiles::add);
        }
        for (ChangedFile changedFile: changedFiles){
            String filename = changedFile.getFilename();
            String rawUrl = changedFile.getRawUrl();
            String status = changedFile.getStatus();
            //"https://github.com/%s/%s/raw/%s/%s"
            //"https://github.com/spring-projects/spring-framework/raw/a05dc97c74b20091c3905beaa0933991ad4133a2/spring-beans%2Fsrc%2Fmain%2Fjava%2Forg%2Fspringframework%2Fbeans%2FAbstractNestablePropertyAccessor.java"
            switch (status){
                case "modified":
                    String oldFileUrl = String.format(Constants.GITHUB.RAW_FILE, owner, repo, parentId, filename);
                    UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(oldFileUrl);
                    String oldFile = builder.build(true).toUriString();
//                    log.info("oldFileUrl = {}", oldFile);
                    fileDownload(oldFile, filename, pullNumber, repo, "prev");
//                    log.info("newFileUrl = {}", rawUrl);
                    fileDownload(rawUrl, filename, pullNumber, repo, "curr");
                    break;
                case "added":
//                    log.info("newFileUrl = {}", rawUrl);
                    fileDownload(rawUrl, filename, pullNumber, repo, "curr");
                    break;
                case "removed":
//                    log.info("oldFileUrl = {}", rawUrl);
                    fileDownload(rawUrl, filename, pullNumber, repo, "prev");
                    break;
                default:
            }
        }
    }

    @SneakyThrows
    public void fileDownload(String url, String filename, String pullNumber, String repo, String base){
        URL u = new URL(url.replace(Constants.GITHUB.GITHUB, Constants.GITHUB.RAW).replace(Constants.GITHUB.RAW_STR, ""));
//        log.info(u.toString());
        //https://raw.githubusercontent.com/spring-projects/spring-framework/a05dc97c74b20091c3905beaa0933991ad4133a2/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
        //https://github.com/spring-projects/spring-framework/raw/a05dc97c74b20091c3905beaa0933991ad4133a2/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java
        BufferedReader reader = new BufferedReader(new InputStreamReader(u.openStream()));
        StringBuilder stringBuilder = new StringBuilder();
        String temp;
        while ((temp = reader.readLine()) != null){
            stringBuilder.append(temp).append("\n");
        }
        String fileContent = stringBuilder.substring(0, stringBuilder.lastIndexOf("\n"));
        String fileParentDir = "";
        int index = filename.lastIndexOf("/");
        if (index != -1){
            fileParentDir = filename.substring(0, index);
        }
//        log.info("fileParentDir = {}", fileParentDir);
        String baseDir = Constants.DEFAULT_OUTPUT_DIR + "/" + repo + "/" + pullNumber + "/" + base + "/" + fileParentDir + "/";
        File dir = new File(baseDir);
        if (! dir.exists()){
            dir.mkdirs();
        }
        File file = new File(baseDir + filename.substring(filename.lastIndexOf("/")+1));
        if (file.exists()) {
            file.delete();
        }
        file.createNewFile();
        FileOutputStream fileOutputStream = new FileOutputStream(file, true);
        fileOutputStream.write(fileContent.getBytes(StandardCharsets.UTF_8));
        fileOutputStream.close();
    }

}
